package com.urbanski.util;

import java.text.DecimalFormat;
import java.util.Arrays;

/**
 * A counter class that, for convenience, maintains a String representation of a specified length,
 * padded with zeroes. This class is useful for generating a sequence of values that will always be
 * padded - for example, generating a number of test usernames ranging from "TEST0001" to
 * "TEST9999". This class will conveniently supply the leading zeroes.
 * 
 * The counter optionally supports rolling over back to the base value when the maximum possible
 * value that can be represented with the specified digits is reached. For example, if the number of
 * digits is 3 and rollover is set to true, then the counter will go from 999 back to 0 (or rather,
 * 000). The same applies to when decrementing - if decrement is called while the value is 0, then
 * the new value becomes equal to the largest possible value that can be displayed with the given
 * number of digits. Otherwise it goes negative.
 * 
 * If rollover is false, then the the value will increase beyond the limit and exceed the specified
 * digits. In the example above, the value would simply become 1000.
 * 
 * @author murbanski004
 * 
 */
public class StringCounter
{
	private int digits;
	private boolean rollover;
	private int value;

	private DecimalFormat format;

	/**
	 * Constructs a new StringCounter instance starting at the value zero that will not rollover and
	 * not pad any zeroes to the front. This is functionally no different from simply maintaining an
	 * int counter.
	 */
	public StringCounter()
	{
		this(0, 0, false);
	}

	/**
	 * Constructs a new StringCounter with the specified number of digits that will not rollover
	 * when the maximum value is reached.
	 * 
	 * @param digits the length of the output string
	 * @throws IllegalArgumentException if digits is negative
	 */
	public StringCounter(int digits)
	{
		this(0, digits, false);
	}

	/**
	 * Constructs a new StringCounter with the specified number of digits and rollover option.
	 * 
	 * @param digits the length of the output string
	 * @param rollover whether to rollover when the value becomes too large to display with the
	 *            specified number of digits
	 * @throws IllegalArgumentException if digits is negative
	 */
	public StringCounter(int digits, boolean rollover)
	{
		this(0, digits, rollover);
	}

	/**
	 * Constructs a new StringCounter with the specified initial value, number of digits and
	 * rollover option.
	 * 
	 * If rollover is true and the initial value is too large to display in the given number of
	 * digits, then it will be automatically rolled over to the correct value, as if the initial
	 * value were zero and the increment was called initialValue times. More formally, the value
	 * will be equal to initialValue % (10 ^ digits), where ^ is the exponent operator.
	 * 
	 * If rollover is true and the initial value is negative, then the initial value will become the
	 * largest possible value given the number of digits: (10 ^ digits) - 1
	 * 
	 * @param initialValue the initial value for the counter
	 * @param digits the length of the output string
	 * @param rollover whether to rollover when the value becomes too large to display with the
	 *            specified number of digits
	 * @throws IllegalArgumentException if digits is negative
	 */
	public StringCounter(int initialValue, int digits, boolean rollover)
	{
		if (digits < 0)
		{
			throw new IllegalArgumentException("Number of digits (" + digits + ") cannot be negative");
		}

		this.digits = digits;
		this.rollover = rollover;
		char[] arr = new char[digits];
		Arrays.fill(arr, '0');
		format = new DecimalFormat(new String(arr));
		setValue(initialValue);
	}

	/**
	 * Raises 10 to the specified power
	 * 
	 * @param p
	 * @return
	 */
	private static int pow(int p)
	{
		int r = 1;
		for (int i = 0; i < p; i++)
		{
			r *= 10;
		}
		return r;
	}

	public void increment()
	{
		value++;
		if (rollover && Integer.toString(value).length() > digits)
		{
			value = 0;
		}
	}

	public void decrement()
	{
		value--;
		if (rollover && value < 0)
		{
			value = pow(digits) - 1;
		}
	}

	public String toString()
	{
		return format.format(value);
	}

	public void setValue(int value)
	{
		int max;

		if (rollover && value < 0)
		{
			value = pow(digits) - 1;
		}
		else if (rollover && value > (max = pow(digits)))
		{
			value = value % max;
		}

		this.value = value;
	}

	public int intValue()
	{
		return value;
	}

	public int getDigits()
	{
		return digits;
	}

	public boolean isRollover()
	{
		return rollover;
	}

	public static void main(String[] args)
	{
		StringCounter sc = new StringCounter(25, 3, true);
		// for (int i = 0; i < 100; i++) sc.increment();
		System.out.println("'" + sc + "'");
		// DecimalFormat df = new DecimalFormat("000");
		// System.out.println(df.format(-5));
	}
}
